package cn.lingyangwl.agile.auth.oauth2.support;

import cn.hutool.core.util.StrUtil;
import cn.lingyangwl.agile.auth.model.mq.LoginLogMQ;
import cn.lingyangwl.agile.auth.model.rqrs.AuthUserUpdateReq;
import cn.lingyangwl.agile.auth.model.rqrs.ConditionLockUserResp;
import cn.lingyangwl.agile.auth.oauth2.Authentication;
import cn.lingyangwl.agile.auth.oauth2.model.RegisteredClient;
import cn.lingyangwl.agile.auth.service.AuthMqService;
import cn.lingyangwl.agile.auth.service.LoginUserService;
import cn.lingyangwl.agile.auth.service.UserService;
import cn.lingyangwl.agile.model.constants.AppType;
import cn.lingyangwl.agile.model.constants.RequestCons;
import cn.lingyangwl.agile.model.constants.UserCons;
import cn.lingyangwl.agile.model.enums.AuthErrorEnum;
import cn.lingyangwl.agile.model.module.auth.LoginUser;
import cn.lingyangwl.agile.model.module.tenant.TenantContext;
import cn.lingyangwl.framework.core.http.HttpRequestServletManager;
import cn.lingyangwl.framework.core.utils.IpUtils;
import cn.lingyangwl.framework.core.utils.servlet.ServletUtils;
import cn.lingyangwl.framework.tool.core.DateUtils;
import cn.lingyangwl.framework.tool.core.exception.BizException;
import eu.bitwalker.useragentutils.UserAgent;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.Objects;
import java.util.Optional;

/**
 * @author shenguangyang
 */
@Slf4j
@Component
public class AuthHandler {
	@Resource
	private LoginUserService loginUserService;
	@Resource
	private AuthMqService authMqService;
	@Resource
	@Lazy
	private UserService userService;

	public void onSuccess(RegisteredClient client, LoginUser authUser) {
		try {
			AppType appType = client.getAppType();
			if (Objects.nonNull(authUser)) {
				loginUserService.unLockUser(authUser.getUserName(), client);
				log.info("用户：{} 登录成功", authUser.getUserName());

				// 绑定成功即认为登录成功
				HttpRequestServletManager servletManager = new HttpRequestServletManager(ServletUtils.getRequestOfNonNull());
				AuthUserUpdateReq updateReq = AuthUserUpdateReq.builder().userId(authUser.getUserId())
						.lastLoginDate(new Date()).lastLoginIp(IpUtils.getRequestIp(servletManager))
						.build();
				userService.updateUser(updateReq, appType);

				// 由于采用多租户方式, 需要用户在登录之后选择登录租户之后才会保存登录租户, 这样登录日志就归属登录的租户下了
			}

		} finally {
			TenantContext.clear();;
		}
	}

	public void onFailure(String account, Authentication authentication, Exception exception) {
		try {
			Optional<Exception> exceptionOpt = Optional.ofNullable(exception);

			log.error("用户：{} 登录失败，异常：{}", account, exceptionOpt.map(Throwable::getLocalizedMessage).orElse("无"));

			// 如果用户连续登录失败一定次数将被锁定
			String willLockedMessage = "";
			String lockedMessage = "";
			if (StringUtils.isNotEmpty(account) && exceptionOpt.isPresent() && exception instanceof BizException) {
				BizException bizException = (BizException) exception;
				ConditionLockUserResp resp;
				if (AuthErrorEnum.USER_USERNAME_OR_PASSWORD_ERROR.equals(bizException.getBaseError())) {
					resp = loginUserService.conditionLockUser(account, authentication);
					if (resp.getHasLimitLoginFail()) {
						if (!resp.getLocked()) {
							willLockedMessage = exception.getLocalizedMessage() + ", " + AuthErrorEnum.WILL_LOCKED.formatMsg(resp.getSurplusLockedCount());
						} else {
							String unLockTime = DateUtils.distance(System.currentTimeMillis(), resp.getUnLockTimestamp(), true);
							lockedMessage = AuthErrorEnum.LOGIN_LOCKED.formatMsg(unLockTime);
						}
					}
				} else if (AuthErrorEnum.LOGIN_LOCKED.equals(bizException.getBaseError())) {
					lockedMessage = bizException.getMessage();
				}
			}

			// 发送异步日志事件
			HttpServletRequest request = ServletUtils.getRequestOfNonNull();
			String startTimeStr = request.getHeader(RequestCons.Headers.REQUEST_START_TIME);
			LoginLogMQ.MsgPayload loginLog = buildLoginLog(account, authentication.getLoginId(), authentication.getClient().getClientId());
			loginLog.setMessage("登录失败");
			loginLog.setStatus(UserCons.Oauth.LOGIN_SUCCESS);
			loginLog.setUserName(account);
			loginLog.setStatus(UserCons.Oauth.LOGIN_FAIL);
			loginLog.setMessage(willLockedMessage + lockedMessage);
			if (StrUtil.isNotBlank(startTimeStr)) {
				Long endTime = System.currentTimeMillis();
				Long startTime = Long.parseLong(startTimeStr);
				loginLog.setCostTime(endTime - startTime);
			}
			authMqService.send(LoginLogMQ.build(loginLog));

			// 处理错误
			processError(exception, willLockedMessage, lockedMessage);
		} finally {

		}
	}

	private void processError(Exception exception,
							  String willLockedMessage, String lockMessage) {
		Integer code = AuthErrorEnum.TOKEN_EXPIRED.getCode();
		HttpStatus status = AuthErrorEnum.TOKEN_EXPIRED.getStatus();
		String msg = StringUtils.defaultIfEmpty(lockMessage, willLockedMessage);

		if (exception instanceof BizException) {
			BizException authException = (BizException) exception;
			if (Objects.nonNull(authException.getBaseError())) {
				code = authException.getCode();
				status = authException.getStatus();
				if (StringUtils.isEmpty(msg)) {
					msg = authException.getMessage();
				}
			}
		}
		ServletUtils.getResponse().setStatus(status.value());
		throw new BizException(code, msg);
	}


	/**
	 * 处理退出成功方法
	 * @param loginUser 登录对象
	 */
	public void onLogout(LoginUser loginUser) {
		try {
			log.info("用户：{} 退出成功", loginUser.getUserName());
			LoginLogMQ.MsgPayload loginLog = buildLoginLog(loginUser.getUserName(), loginUser.getLoginId(), loginUser.getClientId());
			loginLog.setMessage("退出成功");
			// 发送异步日志事件
			Long startTime = System.currentTimeMillis();
			Long endTime = System.currentTimeMillis();
			loginLog.setCostTime(endTime - startTime);

			authMqService.send(LoginLogMQ.build(loginLog));
		} finally {
			// 无状态 注意删除 context 上下文的信息
			SecurityContextHolder.clearContext();
		}
	}


	private LoginLogMQ.MsgPayload buildLoginLog(String account, String loginId, String clientId) {
		LoginLogMQ.MsgPayload payload = new LoginLogMQ.MsgPayload();
		UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequestOfNonNull().getHeader("User-Agent"));
		// 获取客户端操作系统
		String os = userAgent.getOperatingSystem().getName();
		// 获取客户端浏览器
		String browser = userAgent.getBrowser().getName();

		payload.setBrowser(browser);
		payload.setOs(os);
		payload.setLoginId(loginId);
		payload.setClientId(clientId);
		payload.setIp(IpUtils.getRequestIp(new HttpRequestServletManager(ServletUtils.getRequestOfNonNull())));
		payload.setTenantId(TenantContext.get().getTenantId());
		payload.setUserName(account);
		return payload;
	}
}
